Advanced Techniques topic

Advanced Techniques

By now the search model has private writable state, public reads, derived values, reactions, and a scope for grouped lifecycle.

The rest of the package is a set of small tools you add when the model needs to touch another part of the program.

Use batch() when one action updates several sources:

void reset(List<String> documents) {
  batch(() {
    _query.value = '';
    _documents.value = List.unmodifiable(documents);
  });
}

Effects and watchers observe the final state after the outermost batch ends.

Read Without Tracking

Use peek or untracked() when a read is only a snapshot:

final searchId = Signal('local');

final searchTraceEffect = Effect(() {
  final text = session.query.value.trim();
  if (text.isEmpty) return;

  final id = untracked(() => searchId.value);
  print('search $id: $text');
});

searchTraceEffect.dispose();

Changing searchId alone will not re-run the effect.

Wait For A Condition

Use until() when imperative code needs to wait for reactive state:

final documentsLoaded =
    session.documents.until((documents) => documents.isNotEmpty);

The returned Until can be awaited like a future:

session.replaceDocuments([
  'Signals store state',
  'Computed values derive state',
  'Effects run after state changes',
]);

final documents = await documentsLoaded;

By default this wait is independent of any current scope. Pass detach: false when scope disposal should cancel the wait.

Bridge To Streams

Use listen() when another API expects stream-like updates:

final subscription = session.summary.listen(
  analytics.recordSearchSummary,
  immediately: true,
);

Cancel the subscription when that bridge is no longer needed:

await subscription.cancel();

Use Readonly For A Narrower Wrapper

The usual public read surface is a Readable<T> typed reference:

late final Readable<String> query = _query;

Use readonly() when an API specifically wants a Jolt read-only view object:

late final Readonly<String> queryView = _query.readonly();

Both forms prevent assignment through the public type. Readable<T> is the smaller surface; Readonly<T> is a wrapper.

Model Async State

Use AsyncSignal when loading, success, and error should be visible as state:

final remoteResults = AsyncSignal.fromFuture(api.search('signal'));

Read the async state from effects, computed values, or UI code:

final remoteResultLogger = Effect(() {
  final message = remoteResults.map(
    loading: () => 'Searching...',
    success: (results) => '${results.length} remote results',
    error: (error, _) => 'Search failed: $error',
  );

  print(message);
});

remoteResultLogger.dispose();

The async signal can live wherever the surrounding search state lives. Dispose the effect when the logging or UI reaction is no longer needed.

Persist A Value

Use PersistSignal when storage is part of the state model:

final lastQuery = PersistSignal.sync(
  read: () => storage['lastQuery'] ?? '',
  write: (value) => storage['lastQuery'] = value,
);

After writing, wait until the pending storage write has finished:

lastQuery.value = session.query.peek;
await lastQuery.ensureWrite();

Async persistent signals should be initialized with ensure() or getEnsured() before assignment.

These tools are independent. Add them when the surrounding code asks for that kind of connection: a batched command, a one-off snapshot, a stream bridge, an async source, or storage-backed state.

Next Step

When this model moves into Flutter, choose the package to add around the core jolt code in Ecosystem.

Classes

AsyncSignal<T> Advanced Techniques
A reactive signal that exposes the state of an asynchronous source.
ConvertComputed<T, U> Advanced Techniques
A writable computed value that converts through another writable source.
PersistSignal<T> Advanced Techniques
A signal that persists its value to external storage.
Readonly<T> Advanced Techniques
A read-only reactive view that exposes values without write APIs.
Until<T> Advanced Techniques
A cancellable future that completes when a reactive value satisfies a condition.

Extensions

JoltUtilsStreamExtension on Readable<T> Advanced Techniques
Stream interop for Readable values.

Functions

batch<T>(T fn()) → T Advanced Techniques
Runs fn inside a batched notification cycle.
untracked<T>(T fn()) → T Advanced Techniques
Runs fn without recording reactive dependencies.